package org.simpleframework.util;

import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;

/**
 * @author: Oliver
 * @time: 2020/7/18/10:54
 */
@Slf4j
public class ClassUtil {

    public static final String FILE_PROTOCOL = "file";

    /**
     * 获取包下类集合,   <p>Class<?> 避免在某些类型转换时的一些unchecked的错误<p/>
     *
     * @param packageName 包名
     * @return 类集合
     */
    public static Set<Class<?>> extractPackageClass(String packageName) {
        //获取类加载器
        ClassLoader loader = getClassLoader();
        //调用该方法，会返回指定路径下的所有资源。这里传入的路径必须是/分割的
        String replace = packageName.replace(".", "/");
        //2.通过类加载器获取到加载的资源
        URL url = loader.getResource(replace);
        if (url == null) {
            log.info("没有找到指定位置的资源");
            //没有资源，记录日志并返回null
            return null;
        }

        //3.依据不同的资源类型，采用不同的方式获取资源的集合
        Set<Class<?>> classSet = null;
        //说明该资源是本地文件
        if (FILE_PROTOCOL.equals(url.getProtocol())) {
            //创建存放Class的集合
            classSet = new HashSet<>();
            //获取到指定的目录,然后遍历目录获取到该目录下所有的class文件并创建对象
            String pathDerictory = url.getPath();
            //根据字符串的路径创建文件FIle
            File packageDirectory = new File(pathDerictory);
            extractClassFile(classSet, packageDirectory, packageName);
        }
        return classSet;
    }

    /**
     * 递归获取目标package里面的所有class文件(包括子package里的class文件)
     *
     * @param emptyClassSet 装载目标类的集合
     * @param fileSource    文件或者目录
     * @param packageName   包名
     * @return 类集合
     */
    private static void extractClassFile(Set<Class<?>> emptyClassSet, File fileSource, String packageName) {
        //递归终止条件.因为在获取到的files使用了过滤并将class文件进行提取创建了对象,此时能放到files数组的只有目录,而没有文件.
        if (!fileSource.isDirectory()) {
            return;
        }

        //获取到该目录下所有的 目录,并过滤出来class文件对象进行处理
        File[] files = fileSource.listFiles(new FileFilter() {
            @Override
            public boolean accept(File file) {
                if (file.isDirectory()) {
                    return true;
                } else {
                    //获取文件的绝对路径
                    String absoluteFilePath = file.getAbsolutePath();
                    //如果文件以.class结尾,就进行创建对象保存到classSet中
                    if (absoluteFilePath.endsWith(".class")) {
                        //是class直接加载
                        extractClassSet(absoluteFilePath);
                    }
                }
                return false;
            }

            /**
             *根据类的全路径获取到指定package下的class文件并装载到Set中
             *
             * @param absoluteFilePath  class的全路径 例如:
             *                          E:/workspace/springframework//sampleframework/target/classes/com/wyp/entity/dto/MainPageInfoDTO.class
             */
            private void extractClassSet(String absoluteFilePath) {
                //1.从class文件的绝对值路径里提取出包含了package的类名
                //如E:/workspace/springframework/sampleframework/target/classes/com/wyp/entity/dto/MainPageInfoDTO.class
                //需要弄成com.wyp.entity.dto.MainPageInfoDTO
                absoluteFilePath = absoluteFilePath.replace(File.separator, ".");//替换分隔符为.
                absoluteFilePath = absoluteFilePath.substring(absoluteFilePath.indexOf(packageName));//  MainPageInfoDTO.class
                String className = absoluteFilePath.substring(0, absoluteFilePath.lastIndexOf("."));

                //2.通过反射机制获取对应的Class对象并加入到classSet里
                Class<?> aClass = loadClass(className);
                emptyClassSet.add(aClass);
            }

        });
        if (files != null) {
            for (File file : files) {
                //进行递归文件目录
                extractClassFile(emptyClassSet, file, packageName);
            }
        }
    }

    /**
     * 获取Class对象
     *
     * @param className class全名=package + 类名
     * @return Class
     */
    public static Class<?> loadClass(String className) {
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            log.error("load class error:", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取classLoader
     *
     * @return 当前ClassLoader
     */
    public static ClassLoader getClassLoader() {
        //通过当前线程去获取当前线程的类加载器.
        return Thread.currentThread().getContextClassLoader();
    }

    /**
     * 实例化class  根据Class对象创建出 实例对象
     *
     * @param clazz      Class
     * @param <T>        class的类型
     * @param accessible 是否支持私有构造函数创建对象
     * @return 类的实例化对象
     */
    public static <T> T newInstance(Class<?> clazz, boolean accessible) {
        try {
            Constructor constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(accessible);
            return (T) constructor.newInstance();
        } catch (Exception e) {
            log.error("newInstance error", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 为指定字段设置值,也就是依赖注入
     *
     * @param field      字段对象
     * @param targetBean 目标对象
     * @param fieldValue 字段设为的值
     * @param b          是否有权限设置私有属性
     */

    public static void setField(Field field, Object targetBean, Object fieldValue, boolean b) {
        field.setAccessible(b);
        try {
            field.set(targetBean, fieldValue);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        extractPackageClass("com.wyp.service.entity");

    }

}
